
# 语法
$(<function> <arguments>)
${<function> <arguments>}
# <function>就是函数名, make支持的函数不多
# <arguments>为函数的参数, 参数间以逗号分隔, 而函数名和参数之间以"空格"分隔
# 函数调用以$开头, 以圆括号或花括号把函数名和参数括起

# 举例
comma:= ,
empty:=
space:= $(empty) $(empty)
foo:= a b c
bar:= $(subst $(space),$(comma),$(foo))
# bar 为 a,b,c










# 字符串处理函数

# 字符串替换函数
$(subst <from>,<to>,<text>)  # 把字串 <text> 中的 <from> 字符串替换成 <to>
# 示例
$(subst ee,EE,feet on the street)  # 结果是 fEEt on the strEEt


# 模式字符串替换函数
$(patsubst <pattern>,<replacement>,<text>)
# 查找<text>中的单词 (单词以"空格"、"Tab"或"回车""换行"分隔)是否符合模式<pattern>
# 如果匹配的话, 则以<replacement>替换
# 示例
$(patsubst %.c,%.o,x.c.c bar.c)  # 回结果是 x.c.o bar.o
# 备注
$(objects:.o=.c) 和 $(patsubst %.o,%.c,$(objects)) 是一样的


# 去空格函数
$(strip <string>)  # 去掉 <string> 字串中开头和结尾的空字符


# 查找字符串函数
$(findstring <find>,<in>)  # 在字串 <in> 中查找 <find> 字串,返回 <find> 或者空字符串
# 示例
$(findstring a,a b c)  # 返回 a
$(findstring a,b c)  # 返回空


# 过滤函数
$(filter <pattern...>,<text>)
# 以 <pattern> 模式过滤 <text> 字符串中的单词, 保留符合模式 <pattern> 的单词, 可以有多个模式
# 举例
sources := foo.c bar.c baz.s ugh.h
foo: $(sources)
	cc $(filter %.c %.s,$(sources)) -o foo
$(filter %.c %.s,$(sources))  # 返回的值是 foo.c bar.c baz.s


# 反过滤函数
$(filter-out <pattern...>,<text>)
# 以 <pattern> 模式过滤 <text> 字符串中的单词, 去除符合模式 <pattern> 的单词, 可以有多个模式
# 示例
objects=main1.o foo.o main2.o bar.o
mains=main1.o main2.o
$(filter-out $(mains),$(objects))  # 返回值是 foo.o bar.o


# 排序函数
$(sort <list>)  # 给字符串 <list> 中的单词排序(升序)
# 示例
$(sort foo bar lose)  # 返回 bar foo lose


# 取单词函数
$(word <n>,<text>)  # 取字符串 <text> 中第 <n> 个单词 (从一开始)
# 示例
$(word 2, foo bar baz)  # 返回值是 bar


# 取单词串函数
$(wordlist <ss>,<e>,<text>)  
# 从字符串 <text> 中取从 <ss> 开始到 <e> 的单词串
# <ss> 和 <e> 是一个数字
# 如果 <ss> 比 <text> 中的单词数要大那么返回空字符串
# 如果 <e> 大于 <text> 的单词数, 那么返回从 <ss> 开始, 到 <text> 结束的单词串
# 示例
$(wordlist 2, 3, foo bar baz)  # 返回值是 bar baz


# 单词个数统计函数
$(words <text>)  # 统计 <text> 中字符串中的单词个数
# 示例
$(words, foo bar baz)  # 返回值是 3
# 示例 (取最后一个单词)
$(word $(words <text>), <text>)


# 首单词函数
$(firstword <text>)  # 取字符串 <text> 中的第一个单词
# 示例
$(firstword foo bar)  # 返回值是 foo



# 以上是所有的字符串操作函数
# 综合举例
override CFLAGS += $(patsubst %,-I%,$(subst :, ,$(VPATH)))
# 如果我们的 $(VPATH) 值是 src:../headers
# 则将返回 -Isrc -I../headers










# 文件名操作函数

# 取目录函数
$(dir <names...>)  # 从文件名序列 <names> 中取出目录部分
# 目录部分是指最后一个反斜杠(/)之前的部分, 如果没有反斜杠，那么返回 ./
# 示例
$(dir src/foo.c hacks)  # 返回值是 src/ ./


# 取文件函数
$(notdir <names...>)  # 从文件名序列 <names> 中取出非目录部分
# 非目录部分是指最後一个反斜杠(/)之后的部分
# 示例
$(notdir src/foo.c hacks)  # 返回值是 foo.c hacks


# 取后缀函数
$(suffix <names...>)  # 从文件名序列 <names> 中取出各个文件名的后缀
# 示例
$(suffix src/foo.c src-1.0/bar.c hacks)  # 返回值是 .c .c


# 取前缀函数
$(basename <names...>)  # 从文件名序列 <names> 中取出各个文件名的前缀部分
# 示例
$(basename src/foo.c src-1.0/bar.c hacks)  # 返回值是 src/foo src-1.0/bar hacks



# 加后缀函数
$(addsuffix <suffix>,<names...>)  # 把后缀 <suffix> 加到 <names> 中的每个单词后面
# 示例
$(addsuffix .c,foo bar)  # 返回值是 foo.c bar.c


# 加前缀函数
$(addprefix <prefix>,<names...>)  # 把前缀 <prefix> 加到 <names> 中的每个单词后面
# 示例
$(addprefix src/,foo bar)  # 返回值是 src/foo src/bar


# 连接函数
$(join <list1>,<list2>)
# 把 <list2> 中的单词对应地加到 <list1> 的单词后面
# 如果 <list1> 的单词个数要比<list2> 的多, 那么<list1> 中的多出来的单词将保持原样
# 如果 <list2> 的单词个数要比<list1> 多, 那么<list2> 多出来的单词将被复制到 <list1> 中
# 举例
$(join aaa bbb , 111 222 333)  # 返回值是 aaa111 bbb222 333










#  foreach 函数
$(foreach <var>,<list>,<text>)
# 参数 <list> 中的单词逐一取出放到参数 <var> 所指定的变量中, 然后再执行 <text> 所包含的表达式
# 每一次 <text> 会返回一个字符串, 循环过程中, <text> 的所返回的每个字符串会以空格分隔
# 最后当整个循环结束时, <text> 所返回的每个字符串所组成的整个字符串(以空格分隔)将会是 foreach 函数的返回值

# 所以 <var> 最好是一个变量名
# <list> 可以是一个表达式
# 而 <text> 中一般会使用 <var> 这个参数来依次枚举 <list> 中的单词

# 举例
names := a b c d
files := $(foreach n,$(names),$(n).o)  # $(files) 的值是 a.o b.o c.o d.o










# if 函数
$(if <condition>,<then-part>)
$(if <condition>,<then-part>,<else-part>)
# if函数可以包含"else"部分, 或是不含
# 即if函数的参数可以是两个, 也可以是三个
# <condition>参数是if的表达式, 如果其返回的为非空字符串, 那么这个表达式就相当于返回真
# 于是 <then-part>会被计算, 否则<else-part>会被计算

# 而if函数的返回值是, 如果<condition>为真(非空字符串)
# 那个<then-part>会是整个函数的返回值, 如果<condition>为假(空字符串)
# 那么<else-part>会是整个函数的返回值, 此时如果<else-part>没有被定义, 那么, 整个函数返回空字串

# 举例
# 定义一个变量
DEBUG := true

# 使用$(if)函数判断DEBUG变量的值
TARGET := $(if $(DEBUG), debug_target, release_target)

# 输出结果
all:
	@echo "Target: $(TARGET)"










# call 函数
# call 函数是唯一一个可以用来创建新的参数化的函数
$(call <expression>,<parm1>,<parm2>,...,<parmn>)
# 当 make 执行这个函数时, <expression> 参数中的变量, 如 $(1) $(2) 等
# 会被参数 <parm1> <parm2> <parm3> 依次取代
# 而 <expression> 的返回值就是 call 函数的返回值

# 举例
reverse = $(1) $(2)
foo = $(call reverse,a,b)  # foo 的值就是 a b

# 参数的次序是可以自定义的, 不一定是顺序的
reverse = $(2) $(1)
foo = $(call reverse,a,b)  # 此时的 foo 的值就是 b a

# 注意
# 在向 call 函数传递参数时要尤其注意空格的使用
# call函数在处理参数时, 第2个及其之后的参数中的空格会被保留, 因而可能造成一些奇怪的效果
# 因而在向call函数提供参数时, 最安全的做法是去除所有多余的空格

# 举例 2
define my_function
	@echo "Hello, this is my function."
	@echo "Arguments: $1 and $2"
endef

# 调用函数
result := $(call my_function, arg1, arg2)

# 输出结果
all:
	@echo $(result)










# origin 函数
$(origin <variable>)  # 告诉你你的这个变量是哪里来的
# <variable> 是变量的名字, 不应该是引用, 所以最好不要在 <variable> 中使用 $ 字符

# undefined		如果<variable>从来没有定义过, origin函数返回这个值undefined
# default		如果<variable>是一个默认的定义, 比如"CC"这个变量, 这种变量我们将在后面讲述
# environment	如果<variable>是一个环境变量, 并且当Makefile被执行时, -e参数没有被打开
# file			如果<variable>这个变量被定义在Makefile中
# command line	如果<variable>这个变量是被命令行定义的
# override		如果<variable>是被override指示符重新定义的
# automatic		如果<variable>是一个命令运行中的自动化变量

# 举例
# 假设我们有一个Makefile其包了一个定义文件 Make.def
# 在Make.def中定义了一个变量"bletch", 而我们的环境中也有一个环境变量"bletch"
# 此时如果变量来源于环境, 那么我们就把之重定义了
# 如果来源于Make.def或是命令行等非环境的, 那么我们就不重新定义它
ifdef bletch
	ifeq "$(origin bletch)" "environment"
		bletch = barf, gag, etc.
	endif
endif
# 用override是可以达到这样的效果, 可是override过于粗暴, 它同时会把从命令行定义的变量也覆盖了
# 而我们只想重新定义环境传来的, 而不想重新定义命令行传来的









# shell 函数
$(shell <unix commands>)
# shell 函数把执行操作系统命令后的输出作为函数返回

# 举例
contents := $(shell cat foo)
files := $(shell echo *.c)









# 控制 make 的函数
$(error <text ...>)  # 产生一个致命的错误, <text ...> 是错误信息
# error 函数不会在 一被使用 就会产生错误信息
# 所以如果把其定义在某个变量中, 并在后续的脚本中使用这个变量, 那么也是可以的

# 举例 1
ifdef ERROR_001
	$(error error is $(ERROR_001))
endif

# 举例 2
ERR = $(error found an error!)
.PHONY: err
err: $(ERR)  # 只有目标 err 被执行时才会执行 $(error)



$(warning <text ...>)
这个函数很像 error 函数, 只是它并不会让 make 退出, 只是输出一段警告信息, 而 make 继续执行